package seismosurfer.servlets.security;

import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;

import org.securityfilter.realm.SecurityRealmInterface;

import seismosurfer.data.UserData;
import seismosurfer.data.constants.ParameterNames;
import seismosurfer.database.DB;
import seismosurfer.database.UserDAO;
import seismosurfer.domain.SimplePrincipal;

import com.bbn.openmap.util.Debug;

/**
 * A SecurityRealmInterface implementation
 * for Seismo-Surfer.
 *
 */
public class DataSourceRealm implements SecurityRealmInterface {

    public DataSourceRealm() {
    }

    /**
     * Authenticate a user.
     * 
     * @param username
     *            The username of the user to authenticate.
     * @param credentials
     *            The plaintext password, as entered by the user.
     * 
     * @return A Principal object representing the user if successful, null
     *         otherwise.
     */
    public Principal authenticate(String username, String credentials) {
        try {

            // Acquire a Principal object for this user
            return _authenticate(username, credentials);
        } catch (SQLException sqle) {
            Debug.error("Cannot authenticate: " + sqle.getMessage());
            return null;
        } catch (NoSuchAlgorithmException nsae) {
            Debug.error("Nad hash algorithm '" + digest + "'"
                    + nsae.getMessage());
            return null;
        }
    }

    /**
     * Return the Principal associated with the specified username and
     * credentials, if there is one; otherwise return <code>null</code>.
     *       
     *        
     * @param username
     *            Username of the Principal to look up
     * @param credentials
     *            Password or other credentials to use in authenticating this
     *            username
     * 
     * @exception SQLException
     *                if a database error occurs
     */
    private Principal _authenticate(String username, String credentials)
            throws SQLException, NoSuchAlgorithmException {

        PreparedStatement ps = null;
        ResultSet rs = null;        

        try {

            // First, get the user record.
            ps = DB.prepare(getUserSelectStatement());
            ps.setString(1, username);
            ps.setString(2, credentials);

            rs = ps.executeQuery();

            if (!rs.next())
                return null; // No such user

            // Now, get the user's roles
            ArrayList roles = new ArrayList();
            ps = DB.prepare(getRoleSelect());
            ps.setString(1, username);

            rs = ps.executeQuery();

            while (rs.next()) {
                roles.add(rs.getString(1).trim());
            }

            UserData user = UserDAO.findUser(ParameterNames.USERNAME, username);

            if (user == null) {
                Debug.output("User is null despite being authenticated.");
            }

            return new SimplePrincipal(username, roles, user);
        } finally {
            DB.cleanUp(ps, rs);
        }
    }

    
    /**
     * Test for role membership.
     * 
     * Use Principal.getName() to get the username from the principal object.
     * 
     * @param principal
     *            Principal object representing a user
     * @param rolename
     *            name of a role to test for membership
     * 
     * @return true if the user is in the role, false otherwise
     */
    public boolean isUserInRole(Principal principal, String rolename) {
        if ((principal == null) || (rolename == null)
                || !(principal instanceof SimplePrincipal))
            return (false);

        SimplePrincipal p = (SimplePrincipal) principal;

        return p.isInRole(rolename);
    }

    /**
     * The name of the JNDI JDBC DataSource
     */
    protected String dataSourceName;

    /**
     * The table that holds user data.
     */
    protected String userTable;

    /**
     * The column in the user table that holds the user's name
     */
    protected String userNameCol;

    /**
     * The column in the user table that holds the user's credentials (i.e.
     * password).
     */
    protected String userCredCol;

    /**
     * The table that holds the relation between user's and roles
     */
    protected String userRoleTable;

    /**
     * The column in the user role table that names a role
     */
    protected String roleNameCol;

    private String digest;

    /**
     * Return the name of the JNDI JDBC DataSource.
     * 
     */
    public String getDataSourceName() {
        return dataSourceName;
    }

    /**
     * Set the name of the JNDI JDBC DataSource.
     * 
     * @param dataSourceName
     *            the name of the JNDI JDBC DataSource
     */
    public void setDataSourceName(String dataSourceName) {
        this.dataSourceName = dataSourceName;
    }

    /**
     * Return the column in the user role table that names a role.
     * 
     */
    public String getRoleNameCol() {
        return roleNameCol;
    }

    /**
     * Set the column in the user role table that names a role.
     * 
     * @param roleNameCol
     *            The column name
     */
    public void setRoleNameCol(String roleNameCol) {
        this.roleNameCol = roleNameCol;
    }

    /**
     * Return the column in the user table that holds the user's credentials.
     * 
     */
    public String getUserCredCol() {
        return userCredCol;
    }

    /**
     * Set the column in the user table that holds the user's credentials.
     * 
     * @param userCredCol
     *            The column name
     */
    public void setUserCredCol(String userCredCol) {
        this.userCredCol = userCredCol;
    }

    /**
     * Return the column in the user table that holds the user's name.
     * 
     */
    public String getUserNameCol() {
        return userNameCol;
    }

    /**
     * Set the column in the user table that holds the user's name.
     * 
     * @param userNameCol
     *            The column name
     */
    public void setUserNameCol(String userNameCol) {
        this.userNameCol = userNameCol;
    }

    /**
     * Return the table that holds the relation between user's and roles.
     * 
     */
    public String getUserRoleTable() {
        return userRoleTable;
    }

    /**
     * Set the table that holds the relation between user's and roles.
     * 
     * @param userRoleTable
     *            The table name
     */
    public void setUserRoleTable(String userRoleTable) {
        this.userRoleTable = userRoleTable;
    }

    /**
     * Return the table that holds user data..
     * 
     */
    public String getUserTable() {
        return userTable;
    }

    /**
     * Set the table that holds user data.
     * 
     * @param userTable
     *            The table name
     */
    public void setUserTable(String userTable) {
        this.userTable = userTable;
    }

    public void setDigest(String digest) {
        this.digest = digest;
    }

    public String getDigest() {
        return this.digest;
    }

    /**
     * Open the specified database connection.
     * 
     * @return Connection to the database
     */

    private String _userSelect;

    private String _roleSelect;

    private synchronized String getUserSelectStatement() {
        if (null == _userSelect)
            _userSelect = "SELECT " + userNameCol + " FROM " + userTable
                    + " WHERE " + userNameCol + "=? AND " + userCredCol + "=?";

        return _userSelect;
    }

    private synchronized String getRoleSelect() {
        if (null == _roleSelect)
            _roleSelect = "SELECT " + roleNameCol + " FROM " + userRoleTable
                    + " WHERE " + userNameCol + "=?";

        return _roleSelect;
    }
}